package com.jy.datapipeline.export.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.exception.ExcelAnalysisStopException;
import com.alibaba.excel.util.ListUtils;
import com.jy.datapipeline.common.constants.ExpConstants;
import com.jy.datapipeline.common.constants.FormatConstants;
import com.jy.datapipeline.common.constants.PrefixConstants;
import com.jy.datapipeline.common.exception.GlobalException;
import com.jy.datapipeline.common.util.*;
import com.jy.datapipeline.export.common.constants.KeyConstant;
import com.jy.datapipeline.export.dao.ExpTaskDao;
import com.jy.datapipeline.export.dao.repo.ExpTaskDaoRepo;
import com.jy.datapipeline.export.entity.*;
import com.jy.datapipeline.export.common.enums.impl.TaskTypeEnums;
import com.jy.datapipeline.export.quartz.JobDetailExportTaskHandler;
import com.jy.datapipeline.export.service.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.math3.analysis.function.Exp;
import org.quartz.*;
import org.quartz.utils.Key;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Service
@Transactional
@Slf4j
public class ExpTaskServiceImpl implements ExpTaskService {

    private final ExpTaskDao expTaskDao;

    private final ExpTaskDaoRepo expTaskDaoRepo;

    private final Scheduler scheduler;

    private final DataSourceInfoService dataSourceInfoService;

    private final ExpRuleService expRuleService;

    private final DataStoreInfoService dataStoreInfoService;

    private final ExpTaskExecuteDetailLogService expTaskExecuteDetailLogService;

    private final DataDictionaryService dataDictionaryService;

    private final ExpRuleDetailService expRuleDetailService;

    public ExpTaskServiceImpl(ExpTaskDao expTaskDao,
                              ExpTaskDaoRepo expTaskDaoRepo,
                              Scheduler scheduler,
                              DataSourceInfoService dataSourceInfoService,
                              ExpRuleService expRuleService,
                              ExpTaskExecuteDetailLogService expTaskExecuteDetailLogService,
                              DataStoreInfoService dataStoreInfoService, DataDictionaryService dataDictionaryService,
                              ExpRuleDetailService expRuleDetailService) {
        this.expTaskDao = expTaskDao;
        this.expTaskDaoRepo = expTaskDaoRepo;
        this.scheduler = scheduler;
        this.dataSourceInfoService = dataSourceInfoService;
        this.expRuleService = expRuleService;
        this.dataStoreInfoService = dataStoreInfoService;
        this.expTaskExecuteDetailLogService = expTaskExecuteDetailLogService;
        this.dataDictionaryService = dataDictionaryService;
        this.expRuleDetailService = expRuleDetailService;
    }

    @Override
    public ExpTaskModel findByTaskId(String taskId) {

        return this.expTaskDaoRepo.findByTaskId(taskId);
    }

    @Override
    public ExpTaskModel findByTaskName(String taskName) {
        return this.expTaskDaoRepo.findByTaskName(taskName);
    }

    @Override
    public List<ExpTaskModel> findByStatus(int status) {
        return this.expTaskDaoRepo.findByStatus(status);
    }

    @Override
    public List<Map<String, Object>> findExpTaskByList(String taskName, String taskType,
                                                       int iDisplayStart, int iDisplayLength, String storeId, String ruleId, String status, String dataSourceId, String latestStatus) {
        List<Map<String, Object>> expTaskByList = this.expTaskDao.findExpTaskByList(taskName, taskType, iDisplayStart, iDisplayLength, storeId, ruleId, status, dataSourceId, latestStatus);
        //查询最新状态付值给task
        if (!com.mysql.cj.util.StringUtils.isNullOrEmpty(latestStatus) && Integer.parseInt(latestStatus) != 0) {
            for (Map<String, Object> map : expTaskByList) {
                String taskId = (String) map.get("task_id");
                String logDetailExpStatus = expTaskExecuteDetailLogService.queryLatestStatusByTaskId(taskId);
                map.put("log_detail_exp_status", logDetailExpStatus);
            }
        }
        return expTaskByList;
    }

    @Override
    public int findExpTaskByCount(String taskName, String taskType, String storeId, String ruleId, String status, String dataSourceId, String latestStatus) {
        return this.expTaskDao.findExpTaskByCount(taskName,taskType,storeId,ruleId,status,dataSourceId, latestStatus);
    }

    @Override
    public boolean updateExpTask(ExpTaskModel etm) {
        //更新规则详情的isAccurate字段
        expRuleDetailService.updateExpRuleDetailIsAccurate(etm);
        //更新任务
        boolean isResult=this.expTaskDao.updateExpTask(etm);
        if(isResult){
            try{
                if(etm.getTaskType()==2){
                TriggerKey triggerKey = TriggerKey.triggerKey(etm.getTaskId());
                CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
                String cronExpression = cronTrigger.getCronExpression();
                if(!cronExpression.equals(etm.getCrontab())) {
                    cronTrigger = cronTrigger.getTriggerBuilder()
                        .withIdentity(etm.getTaskId())
                        .withSchedule(CronScheduleBuilder
                            .cronSchedule(etm.getCrontab())
                            .withMisfireHandlingInstructionDoNothing())
                        .build();
                    scheduler.rescheduleJob(triggerKey, cronTrigger);
                }
                }
            }catch (Exception e){
                log.error("updateExpTask error",e);
                    return  false;
            }

        }
        return isResult;
    }
    
    @Override
    public boolean saveExpTask(ExpTaskModel etm) {
        try {
            //更新规则的isAccurate字段
            expRuleDetailService.updateExpRuleDetailIsAccurate(etm);
            //新增任务
            this.expTaskDaoRepo.save(etm);
            this.createScheduledTask(etm);

//            if(etm.getTaskType()==2) {
//                JobDetail jobDetail = JobBuilder.newJob(JobDetailExportTaskHandler.class)
//                    .withIdentity(etm.getTaskId()).build();
//                jobDetail.getJobDataMap().put("id", etm.getTaskId());
//                //通过指定开始时间和cron表达式,创建触发器
//                Trigger trigger = TriggerBuilder.newTrigger()
//                    .withIdentity(etm.getTaskId())
//                    .withSchedule(CronScheduleBuilder.cronSchedule(etm.getCrontab())
//                        //如果cron表达式指定的时间错过了当前的这个时间点就会忽略，等待下一次到达这个时间点执行
//                        .withMisfireHandlingInstructionDoNothing()).build();
//
//                //关联触发器和任务
//                scheduler.scheduleJob(jobDetail, trigger);
//
//            }
            return true;
        }catch (Exception e){
            log.error("");
            return false;
        }
    }

    private void createScheduledTask(ExpTaskModel etm) throws SchedulerException {
        if(etm.getTaskType()==2) {
            JobDetail jobDetail = JobBuilder.newJob(JobDetailExportTaskHandler.class)
                    .withIdentity(etm.getTaskId()).build();
            jobDetail.getJobDataMap().put("id", etm.getTaskId());
            //通过指定开始时间和cron表达式,创建触发器
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(etm.getTaskId())
                    .withSchedule(CronScheduleBuilder.cronSchedule(etm.getCrontab())
                            //如果cron表达式指定的时间错过了当前的这个时间点就会忽略，等待下一次到达这个时间点执行
                            .withMisfireHandlingInstructionDoNothing()).build();

            //关联触发器和任务
            scheduler.scheduleJob(jobDetail, trigger);
        }
    }

    @Override
    public boolean deleteById(int id) {
        this.expTaskDaoRepo.deleteById(id);
        return true;
    }

    @Override
    public boolean deleteExpTask(String taskId) {

        boolean isResult= expTaskDao.deleteExpTask(taskId);
        try {

        }catch (Exception e){
            log.error("deleteExpTask error",e);
        }

        return isResult;
    }

    @Override
    public boolean updateExpTaskStatus(int status, String taskId,String netExecuTime) {
        return expTaskDao.updateExpTaskStatus(status,taskId,netExecuTime);
    }

    @Override
    public List<Map<String, Object>> findExecuTask() {
        return expTaskDao.findExecuTask();
    }

    @Override
    public List<ExpTaskModel> findByStoreId(String storeId) {
        return this.expTaskDaoRepo.findByStoreId(storeId);
    }

    @Override
    public List<ExpTaskModel> findByRuleId(String ruleId) {
        return this.expTaskDaoRepo.findByRuleId(ruleId);
    }

    @Override
    public <T> T analysisExcelRows(T data) {
        ExpTaskTemplateModel expTaskTemplateModel = ConvertUtil.convertObject(data, ExpTaskTemplateModel.class);
        //非空校验
        AssertUtil.isNull(expTaskTemplateModel.getTaskName(), ExpConstants.EXP_TASK_NAME_IS_NULL);
        AssertUtil.isNull(expTaskTemplateModel.getExpDataSource(), ExpConstants.EXP_DATA_SOURCE_IS_NULL);
        AssertUtil.isNull(expTaskTemplateModel.getExpRule(), ExpConstants.EXP_RULE_NAME_IS_NULL);
        AssertUtil.isNull(expTaskTemplateModel.getExpStore(), ExpConstants.EXP_STORE_NAME_IS_NULL);
        AssertUtil.isNull(expTaskTemplateModel.getCity(), ExpConstants.EXP_CITY_IS_NULL);

        //校验任务名称是否相同
        ExpTaskModel taskName = expTaskDaoRepo.findByTaskName(expTaskTemplateModel.getTaskName());
        AssertUtil.isNotNUll(taskName, ExpConstants.SAME_TASK_NAME_EXP);

        //检查城市编码是否存在
        boolean cityCodeFlag = false;
        List<DataDictionary> cityCodeList = dataDictionaryService.findByType(ExpConstants.DIC_TYPE_CITY_CODE);
        for (DataDictionary dataDictionary : cityCodeList) {
            if (dataDictionary.getLabelName().equals(expTaskTemplateModel.getCity())) {
                cityCodeFlag = true;
                break;
            }
        }
        if (!cityCodeFlag) {
            throw new ExcelAnalysisStopException(ExpConstants.CITY_CODE_NOT_EXISTS);
        }

        if (expTaskTemplateModel.getTaskType() == 0) {
            throw new ExcelAnalysisStopException(ExpConstants.EXP_TASK_TYPE_IS_NULL);
        }

        //校验是否存在此数据源，以及该数据源下是否存在此导出规则
        String expDataSourceName = expTaskTemplateModel.getExpDataSource();
        DataSourceInfoModel dataSourceInfoModel = dataSourceInfoService.findByDataSourceName(expDataSourceName);
        if (ObjectUtil.isNull(dataSourceInfoModel)) {
            throw new ExcelAnalysisStopException(ExpConstants.EXP_DATA_SOURCE_NOT_EXISTS);
        }
        String dataSourceId = dataSourceInfoModel.getDataSourceId();
        expTaskTemplateModel.setDataSourceId(dataSourceId);
        List<ExpRuleModel> expRuleModels = expRuleService.findByDataSourceId(dataSourceId);
        if (ObjectUtil.isNull(expRuleModels) || expRuleModels.size() == 0) {
            throw new ExcelAnalysisStopException(ExpConstants.EXP_DATA_SOURCE_NOT_RULE);
        }
        boolean ruleFlag =false;
        String ruleId = null;
        for (ExpRuleModel expRuleModel : expRuleModels) {
            if (expRuleModel.getRuleName().equals(expTaskTemplateModel.getExpRule())) {
                //如果存在这个导出规则，就获取此ruleid
                ruleId = expRuleModel.getRuleId();
                ruleFlag = true;
                break;
            }
        }
        if (!ruleFlag) {
            throw new ExcelAnalysisStopException(ExpConstants.EXP_RULE_NOT_EXISTS);
        }
        expTaskTemplateModel.setRuleId(ruleId);

        //校验是否存在此存储名称
        DataStoreInfoModel dataStoreInfoModel = dataStoreInfoService.findByStoreName(expTaskTemplateModel.getExpStore());
        if (ObjectUtil.isNull(dataStoreInfoModel)) {
            throw new ExcelAnalysisStopException(ExpConstants.EXP_STORE_NAME_NOT_EXISTS);
        }
        expTaskTemplateModel.setStoreId(dataStoreInfoModel.getStoreId());

        //任务类型只能是手动或者定时两类
        Integer expTaskType = expTaskTemplateModel.getTaskType();
        if (!expTaskType.equals(TaskTypeEnums.HANDLE_TYPE.getCode()) && !expTaskType.equals(TaskTypeEnums.AUTO_TYPE.getCode())) {
            throw new ExcelAnalysisStopException(ExpConstants.EXP_TASK_TYPE_CONSTRAINT);
        }

        //任务类型如果是定时任务，则必须有定时时间，同时对定时时间格式进行校验
        if (expTaskType.equals(TaskTypeEnums.AUTO_TYPE.getCode())) {
            String crontab = expTaskTemplateModel.getCrontab();
            if (expTaskTemplateModel.getCrontab() != null) {
                crontab = crontab.trim();
            }
            if (StringUtils.isEmpty(crontab)) {
                throw new ExcelAnalysisStopException(ExpConstants.EXP_CRONTAB_NOT_EXISTS);
            }
            boolean isValid = CronExpression.isValidExpression(crontab);
            if (!isValid) {
                throw new ExcelAnalysisStopException(crontab + ExpConstants.EXP_CRONTAB_IS_ILLEGAL);
            }
        }
        return (T) expTaskTemplateModel;
    }

    @SneakyThrows
    @Override
    public <T> Set<Long> batchSave(List<T> t) {
        List<ExpTaskTemplateModel> expTaskTemplateModelList = ConvertUtil.convertArray(t, ExpTaskTemplateModel.class);
        List<ExpTaskModel> expTaskModelList = ListUtils.newArrayList();
        ExpTaskModel expTaskModel;
        Date netDate;
        for (ExpTaskTemplateModel expTaskTemplateModel : expTaskTemplateModelList) {
            expTaskModel = new ExpTaskModel();
            expTaskModel.setTaskId(PrefixConstants.TASK_PREFIX + "_" + SnowFlakeUtil.getNextId());
            if (expTaskTemplateModel.getCrontab() != null) {
                netDate = DateUtils.parseDate(CronUtils.nextExecution(expTaskTemplateModel.getCrontab()), FormatConstants.DATETIME_FORMAT);
                expTaskModel.setNextexec_time(netDate);
            }
            expTaskModel.setStatus(0);
            expTaskModel.setStoreId(expTaskTemplateModel.getStoreId());
            List<DataDictionary> cityCodeList = dataDictionaryService.findByType(ExpConstants.DIC_TYPE_CITY_CODE);
            for (DataDictionary dataDictionary : cityCodeList) {
                if (dataDictionary.getLabelName().equals(expTaskTemplateModel.getCity())) {
                    expTaskModel.setCityCodeId(dataDictionary.getId());
                }
            }
            BeanUtils.copyProperties(expTaskTemplateModel, expTaskModel);
            expTaskModelList.add(expTaskModel);
        }
        Set<Integer> idSet = expTaskDao.batchSaveExpTask(expTaskModelList);
        for (ExpTaskModel expTask : expTaskModelList) {
            if (expTask.getCrontab() != null) {
                this.createScheduledTask(expTask);
            }
        }

        Set<Long> ids = new HashSet<>();
        for (Integer id : idSet) {
            ids.add((long) id);
        }
        return ids;
    }

    @Override
    public <T> Long save(T t) {
        ExpTaskTemplateModel expTaskTemplateModel = (ExpTaskTemplateModel) t;
        ExpTaskModel expTaskModel = new ExpTaskModel();
        expTaskModel.setTaskId(PrefixConstants.TASK_PREFIX + "_" + SnowFlakeUtil.getNextId());
        Date netDate = DateUtils.parseDate(CronUtils.nextExecution(expTaskTemplateModel.getCrontab()), FormatConstants.DATETIME_FORMAT);
        expTaskModel.setNextexec_time(netDate);
        expTaskModel.setStatus(0);
        expTaskModel.setStoreId(expTaskTemplateModel.getStoreId());
        List<DataDictionary> cityCodeList = dataDictionaryService.findByType(ExpConstants.DIC_TYPE_CITY_CODE);
        for (DataDictionary dataDictionary : cityCodeList) {
            if (dataDictionary.getLabelName().equals(expTaskTemplateModel.getCity())) {
                expTaskModel.setCityCodeId(dataDictionary.getId());
            }
        }
        BeanUtils.copyProperties(expTaskTemplateModel, expTaskModel);
        saveExpTask(expTaskModel);
        return (long) expTaskModel.getId();
    }

    @SneakyThrows
    @Override
    public void batchDelete(Set<Long> ids) {
        expTaskDao.batchDeleteExpTask(ids);
        //查询taskid
        Set<String> taskIds = expTaskDao.findTaskIds(ids);
        for (String taskId : taskIds) {
            scheduler.deleteJob(JobKey.jobKey(taskId, Key.DEFAULT_GROUP));
        }
    }
}
